package com.jh.loadbalance.server.mvc.aop;

import com.jh.loadbalance.server.mvc.annotation.SemaphoreCircuitBreaker;
import com.jh.loadbalance.server.mvc.annotation.TimeoutCircuitBreaker;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

@Aspect
@Component
public class ServerControllerAdvince {

    private ExecutorService executorService = Executors.newFixedThreadPool(20);

    private Semaphore semaphore = null;

    // 无注解实现
    @Around("execution(* com.jh.loadbalance.server.controller.SeverController.advinceMessage(..)) && args(message)")
    public Object doAroundNoAnnotation(ProceedingJoinPoint pjp, String message) throws Throwable {
        return invokeFutureMethod(pjp, message, 100);
    }

    // timeout注解实现
    /*@Around("execution(* com.jh.loadbalance.server.controller.SeverController.annotationTimeoutMessage(..)) && args(message) && @annotation(circuitBreaker)")
    public Object doAroundTimeoutAnnotation(ProceedingJoinPoint pjp, String message, TimeoutCircuitBreaker circuitBreaker) throws Throwable {
        // 使用 @annotation 去获取timeout
        long timeout = circuitBreaker.timeout();
        return invokeFutureMethod(pjp, message, timeout);
    }*/

    @Around("execution(* com.jh.loadbalance.server.controller.SeverController.annotationTimeoutMessage(..)) && args(message)")
    public Object doAroundTimeoutAnnotation2(ProceedingJoinPoint pjp, String message) throws Throwable {
        // 使用 反射 去获取timeout，通用性较强
        long timeout = -1;
        if (pjp instanceof MethodInvocationProceedingJoinPoint) {
            MethodInvocationProceedingJoinPoint methodPoint = (MethodInvocationProceedingJoinPoint) pjp;
            MethodSignature signature = (MethodSignature) methodPoint.getSignature();
            Method method = signature.getMethod();
            TimeoutCircuitBreaker timeoutCircuitBreaker = method.getAnnotation(TimeoutCircuitBreaker.class);
            timeout = timeoutCircuitBreaker.timeout();
        }
        return invokeFutureMethod(pjp, message, timeout);
    }

    // 信号量实现
    @Around("execution(* com.jh.loadbalance.server.controller.SeverController.annotationSemaphoreMessage(..)) " +
            "&& args(message) " +
            "&& @annotation(circuitBreaker)")
    public Object doAroundSemaphoreAnnotation(ProceedingJoinPoint pjp,
                                              String message,
                                              SemaphoreCircuitBreaker circuitBreaker) throws Throwable {

        int value = circuitBreaker.value();

        if (Objects.isNull(semaphore)) {
            semaphore = new Semaphore(value);
        }

        Object result = null;

        try {
            if (semaphore.tryAcquire()) {
                result = pjp.proceed(new Object[]{message});
                semaphore.release();
            } else {
                result = "Fault";
            }
        } catch (Exception ex){
            semaphore.release();
        }

        return result;
    }


    private Object invokeFutureMethod(ProceedingJoinPoint pjp, String message, long timeout) throws Throwable {
        Future<Object> future = executorService.submit(() -> {
            Object result = null;
            try {
                result = pjp.proceed(new Object[]{message});
            } catch (Throwable throwable) {
            }
            return result;
        });

        Object result = null;
        try {
            result = future.get(timeout, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            future.cancel(true);
            result = "Fault";
        }

        return result;
    }

    @PreDestroy
    public void preDestroy() {
        executorService.shutdown();
    }
}
